﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using WMM.Tapioca.Linkbase;
using WMM.Tapioca.Taxonomy;
using WMM.Tapioca.Instance;
using System.Xml.Schema;
using System.Xml.Linq;
using WMM.Tapioca.Utilities;
using System.Web;
using System.Collections;

namespace WMM.Tapioca.Validate
{
    /// <summary>
    /// Class that validates the Linkbases files.
    /// </summary>
    public class ValidateLinkbases
    {
        private static readonly log4net.ILog log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);

        private DTS dts;

        /// <summary>
        /// The constructor.
        /// </summary>
        public ValidateLinkbases(DTS dts)
        {
            this.dts = dts;

            log.Info("Validating linkbases, count: " + dts.LinkbaseDocuments.Count);
            Controller.Instance.RaiseModelChange(this, new ModelChangeEventArgs("Validating linkbases, count: " + dts.LinkbaseDocuments.Count));

            //ValidateLinkbaseInstance();

            ValidateCycleInLinkbase();
            ValidateRoleTypeAndArcRoleType();
            ValidateUsedOnInRoleType();
            ValidateUsedOnInArcRoleType();
            ValidateRoleRefAndArcRoleRef();
            ValidateArcRoleInArcs();
            ValidateBalanceInSummation();
            ValidateCalculationLinkbase();
            ValidateOverrideLabelAndReferenceLinkbases();
            ValidatePreferredLabelAttribute();
            ValidateArcsDuplication();
            ValidateArcsInDefinitionArcRoles();
            ValidateRoleLinkbaseRef();
            ValidateHrefResolution();
        }


        /// <summary>
        /// Validate linkbase document.
        /// </summary>
        private void ValidateLinkbaseInstance()
        {
            ValidationEventHandler eventHandler = new ValidationEventHandler(ValidationHandler);

            foreach (LinkbaseDocument linkbase in dts.LinkbaseDocuments)
            {
                try
                {
                    XmlSchemaSet xmlSchemaSet = new XmlSchemaSet();

                    foreach (TaxonomySchema taxonomySchema in dts.TaxonomySchemas)
                    {
                        //if (taxonomySchema.ParentDocument != null && taxonomySchema.ParentDocument == linkbase)
                        //{
                        //xmlSchemaSet.XmlResolver = resolver;
                        //xmlSchemaSet.Add(taxonomySchema.TargetNamespace, taxonomySchema.XmlSchema.SourceUri);
                        xmlSchemaSet.Add(taxonomySchema.XmlSchema);
                        //}
                    }

                    RemoveSchemaDuplicate(xmlSchemaSet);

                    log.Info("Validating linkbase: " + linkbase.FileName + " targetNamespace:" + linkbase.TargetNamespace);
                    linkbase.ThisXDocument.Validate(xmlSchemaSet, eventHandler, false);
                }
                catch (Exception ex)
                {
                    log.Error("Validation error on ValidateLinkbaseInstance, linkbase: " + linkbase.FileName, ex);
                    dts.ValidatedLinkbases = false;
                }
            }
        }


        private void ValidationHandler(object sender, ValidationEventArgs e)
        {
            log.Error("Validation error on LinkbaseInstance, LineNumber:" + e.Exception.LineNumber + ":" +  e.Exception.LinePosition, new XbrlException(e.Message));
            dts.ValidatedLinkbases = false;
        }


        private static void RemoveSchemaDuplicate(XmlSchemaSet xmlSchemaSet)
        {
            // Using an ArrayList lets use indexing for accessing the schemas.
            ArrayList schemaList = new ArrayList(xmlSchemaSet.Schemas());

            // ArrayList holding the duplicate schemas.
            ArrayList duplicateSchemaList = new ArrayList();

            for (int i = 0; i < schemaList.Count; i++)
            {
                // Exhaustively test for duplicate schemas by means of the
                // SourceUri property.
                for (int j = i + 1; j < schemaList.Count; j++)
                {
                    if (((XmlSchema)schemaList[i]).TargetNamespace ==
                        ((XmlSchema)schemaList[j]).TargetNamespace)
                    {
                        //duplicateSchemaList.Add((XmlSchema)schemaList[j]);
                        xmlSchemaSet.Remove((XmlSchema)schemaList[j]);
                    }
                }
            }
        }


        /// <summary>
        /// A method that validate weather the linkbase document has cycle.
        /// </summary>
        public void ValidateCycleInLinkbase()
        {
            Controller.Instance.RaiseModelChange(this, new ModelChangeEventArgs("Validating Cycle arcs network..."));

            foreach (NetworkGraph ng in dts.NetworkGraphs)
            {
                // A definition network
                if (ng.TupleNG.Item1.Equals("definitionLink"))
                {
                    if (IsCustomArcRole(ng.TupleNG.Item4))
                    {
                        ArcRoleType arcRoleType = GetArcToleType(ng.TupleNG.Item4);

                        if (arcRoleType.CyclesAllowed.Equals("none"))
                        {
                            if (ng.HasCycle())
                            {
                                log.Error("Validation error on linkbase", new XbrlException("A definition network of " + ng.TupleNG.Item4 + " arc roles that contain a cycle"));
                                dts.ValidatedLinkbases = false;
                            }
                        }

                        if (arcRoleType.CyclesAllowed.Equals("undirected"))
                        {
                            if (ng.HasDirectedCycle())
                            {
                                log.Error("Validation error on linkbase", new XbrlException("A definition network of " + ng.TupleNG.Item4 + " arc roles that contain a directed cycle"));
                                dts.ValidatedLinkbases = false;
                            }
                        }
                    }
                    else
                    {
                        //Only undirected cycles are allowed in networks of general-special arcs. arcrole="http://www.xbrl.org/2003/arcrole/general-special"
                        //Only undirected cycles are allowed in networks of essence-alias arcs. http://www.xbrl.org/2003/arcrole/essence-alias
                        //Fully conformant XBRL processors MUST detect any directed cycles in networks of essence-alias arcs.

                        if (ng.HasDirectedCycle())
                        {
                            log.Error("Validation error on linkbase", new XbrlException("A definition network of role: " + ng.TupleNG.Item2 + " and arcrole: " + ng.TupleNG.Item4 + " arc roles that contain a directed cycle"));
                            dts.ValidatedLinkbases = false;
                        }
                    }
                }

                // A presentation network
                else if (ng.TupleNG.Item1.Equals("presentationLink"))
                {
                    if (IsCustomArcRole(ng.TupleNG.Item4))
                    {
                        ArcRoleType arcRoleType = GetArcToleType(ng.TupleNG.Item4);

                        if (arcRoleType.CyclesAllowed.Equals("none"))
                        {
                            if (ng.HasCycle())
                            {
                                log.Error("Validation error on linkbase", new XbrlException("A presentation network of " + ng.TupleNG.Item4 + " arc roles that contain a cycle"));
                                dts.ValidatedLinkbases = false;
                            }
                        }

                        if (arcRoleType.CyclesAllowed.Equals("undirected"))
                        {
                            if (ng.HasDirectedCycle())
                            {
                                log.Error("Validation error on linkbase", new XbrlException("A presentation network of role: " + ng.TupleNG.Item2 + " and arcrole: " + ng.TupleNG.Item4 + " arc roles that contain a directed cycle"));
                                dts.ValidatedLinkbases = false;
                            }
                        }
                    }
                    else
                    {
                        //directed cycles are not allowed in networks of parent-child relationships
                        //arcrole="http://www.xbrl.org/2003/arcrole/parent-child"

                        if (ng.HasDirectedCycle())
                        {
                            log.Error("Validation error on linkbase", new XbrlException("A presentation network of role: " + ng.TupleNG.Item2 + " and arcrole: " + ng.TupleNG.Item4 + " arc roles that contain a directed cycle"));
                            dts.ValidatedLinkbases = false;
                        }
                    }
                }

                // A calculation network
                else if (ng.TupleNG.Item1.Equals("calculationLink"))
                {
                    // The arc role http://www.xbrl.org/2003/role/summation-item is treated as if it
                    // were declared with link:arcroleType cyclesAllowed attribute equal to
                    // ‘undirected’

                    //summation-item arcs may form any cycles
                    //if (ng.HasDirectedCycle())
                    //{
                    //    log.Error("Validation error on linkbase", new XbrlException("A calculation network of " + ng.TupleNG.Item4 + " arc roles that contain a directed cycle"));
                    //    dts.ValidatedLinkbases = false;
                    //}
                }
            }


        }


        private ArcRoleType GetArcToleType(string arcRole)
        {
            foreach (ArcRoleType arcRoleType in dts.ArcRoleTypes)
            {
                if (arcRoleType.ArcRoleUri.AbsoluteUri.Equals(arcRole))
                {
                    return arcRoleType;
                }
            }

            return null;
        }


        private void ValidateRoleTypeAndArcRoleType()
        {
            Controller.Instance.RaiseModelChange(this, new ModelChangeEventArgs("Validating linkbases: RoleTypeAndArcRoleType"));

            //RoleType
            ValidateRoleType();

            //ArcRoleType
            ValidateArcRoleType();
        }


        private void ValidateRoleType()
        {
            foreach (RoleType roleType in dts.RoleTypes)
            {
                string roleURI = roleType.RoleUri.ToString();

                foreach (RoleType roleTypeToCompare in dts.RoleTypes)
                {
                    if (roleType != roleTypeToCompare)
                    {
                        // This is not checked for linkbaseRefs
                        if (roleType.UsedOnReferences.Count == 1 && roleType.UsedOnReferences[0].LocalName.Equals("linkbaseRef") &&
                            roleTypeToCompare.UsedOnReferences.Count == 1 && roleTypeToCompare.UsedOnReferences[0].LocalName.Equals("linkbaseRef"))
                        {
                            return;
                        }

                        if (roleType.Schema == roleTypeToCompare.Schema && roleTypeToCompare.RoleUri.ToString().Equals(roleURI))
                        {
                            log.Error("Validation error: DTS contains two role references to the same URI: " + roleURI);
                            dts.ValidatedLinkbases = false;
                            return;
                        }

                        if (roleType.Schema != roleTypeToCompare.Schema && !IsEquivalentRoleType(roleType, roleTypeToCompare)
                            && roleTypeToCompare.RoleUri.ToString().Equals(roleURI))
                        {
                            log.Error("Validation error: DTS contains two role references to the same URI: " + roleURI);
                            dts.ValidatedLinkbases = false;
                        }
                    }
                }
            }

            // Within a roleType element there MUST NOT be s-equal usedOn elements.

            foreach (RoleType roleType in dts.RoleTypes)
            {
                for (int i = 0; i < 1; i++)
                {
                    for (int j = 0; j < roleType.UsedOnReferences.Count; j++)
                    {
                        if (i != j)
                        {
                            if (roleType.UsedOnReferences[i].LocalName.Equals(roleType.UsedOnReferences[j].LocalName) &&
                                roleType.UsedOnReferences[i].Namespace.Equals(roleType.UsedOnReferences[j].Namespace))
                            {
                                log.Error("Validation error: Within a roleType element there MUST NOT be s-equal usedOn elements. UsedOn: " + roleType.UsedOnReferences[i]);
                                dts.ValidatedLinkbases = false;
                            }
                        }
                    }
                }
            }
        }


        private void ValidateArcRoleType()
        {
            foreach (ArcRoleType arcRoleType in dts.ArcRoleTypes)
            {
                string arcRoleUri = arcRoleType.ArcRoleUri.AbsoluteUri;

                foreach (ArcRoleType arcRoleTypeToCompare in dts.ArcRoleTypes)
                {
                    if (arcRoleType != arcRoleTypeToCompare)
                    {
                        if (arcRoleType.Schema == arcRoleTypeToCompare.Schema && arcRoleTypeToCompare.ArcRoleUri.AbsoluteUri.Equals(arcRoleUri))
                        {
                            log.Error("Validation error: The same taxonomy schema document declares a new arc role twice. arcroleURI: " + arcRoleUri);
                            dts.ValidatedLinkbases = false;
                        }

                        if (arcRoleType.Schema != arcRoleTypeToCompare.Schema && !IsEquivalentArcRoleType(arcRoleType, arcRoleTypeToCompare)
                            && arcRoleTypeToCompare.ArcRoleUri.AbsoluteUri.Equals(arcRoleUri))
                        {
                            log.Error("Validation error: Different taxonomy schema document declare a new arc role twice and the declarations are not equivalent. arcroleURI: " + arcRoleUri);
                            dts.ValidatedLinkbases = false;
                        }
                    }
                }
            }
        }


        private bool IsEquivalentRoleType(RoleType roleType, RoleType roleTypeToCompare)
        {
            if (roleType.RoleUri.Equals(roleTypeToCompare.RoleUri) &&
                roleType.Id.Equals(roleTypeToCompare.Id) && IsEquivalentUsedOn(roleType, roleTypeToCompare))
            {
                return true;
            }

            return false;
        }


        private bool IsEquivalentArcRoleType(ArcRoleType arcRoleType, ArcRoleType arcRoleTypeToCompare)
        {
            if (arcRoleType.ArcRoleUri.AbsoluteUri.Equals(arcRoleTypeToCompare.ArcRoleUri.AbsoluteUri) && 
                arcRoleType.CyclesAllowed.Equals(arcRoleTypeToCompare.CyclesAllowed) &&
                arcRoleType.Id.Equals(arcRoleTypeToCompare.Id) &&
                IsEquivalentUsedOn(arcRoleType, arcRoleTypeToCompare))
            {
                return true;
            }

            return false;
        }


        private bool IsEquivalentUsedOn(RoleType roleType, RoleType roleTypeToCompare)
        {
            if (roleType.UsedOnReferences.Count != roleTypeToCompare.UsedOnReferences.Count)
            {
                return false;
            }

            foreach (XName usedOn in roleType.UsedOnReferences)
            {
                if (!roleTypeToCompare.UsedOnReferences.Contains(usedOn))
                {
                    return false;
                }
            }

            return true;
        }


        private bool IsEquivalentUsedOn(ArcRoleType arcRoleType, ArcRoleType arcRoleTypeToCompare)
        {
            if (arcRoleType.UsedOnReferences.Count != arcRoleTypeToCompare.UsedOnReferences.Count)
            {
                return false;
            }

            foreach (XName usedOn in arcRoleType.UsedOnReferences)
            {
                if (!arcRoleTypeToCompare.UsedOnReferences.Contains(usedOn))
                {
                    return false;
                }
            }

            return true;
        }


        /// <remarks>
        /// XBRL 2.1 Specification 5.1.3.4 
        /// The roleType element MAY contain one or more usedOn elements. The usedOn element identifies which 
        /// elements MAY use the role type being defined. Within a roleType element there MUST NOT be s-equal
        /// usedOn elements. Standard extended link elements and standard resource elements that use the defined 
        /// role type MUST be identified with a usedOn element in the roleType element. Note that custom extended
        /// link elements and custom resource elements are not governed by this constraint.
        private void ValidateUsedOnInRoleType()
        {
            Controller.Instance.RaiseModelChange(this, new ModelChangeEventArgs("Validating linkbases: UsedOnInRoleType"));

            foreach (LinkbaseDocument linkbase in dts.LinkbaseDocuments)
            {
                foreach (ExtendedLink extendedLink in linkbase.ExtendedLinks)
                {
                    foreach (RoleType roleType in dts.RoleTypes)
                    {
                        if (IsStandardExtendedLink(extendedLink.LocalName) && extendedLink.ExtendedLinkRole.Equals(roleType.RoleUri.AbsoluteUri))
                        {
                            if (!roleType.UsedOnReferences.Contains(XName.Get(extendedLink.LocalName, extendedLink.NamespaceUri)))
                            {
                                log.Error("A new role intended for used on a specific extended Link element, however it is used on a other extended Link element(" + extendedLink.LocalName + ").");
                                dts.ValidatedLinkbases = false;
                            }
                        }

                        foreach (Resource resource in extendedLink.Resources)
                        {
                            if ((resource.LocalName.Equals("label") || resource.LocalName.Equals("reference")) && resource.Role.Equals(roleType.RoleUri.AbsoluteUri))
                            {
                                if (!roleType.UsedOnReferences.Contains(XName.Get(resource.LocalName, resource.NamespaceUri)))
                                {
                                    log.Error("A new role intended for used on a specific resource, however it is used on a other resource element(" + resource.LocalName + ").");
                                    dts.ValidatedLinkbases = false;
                                }
                            }
                        }
                    }
                }
            }
        }        


        /// XBRL 2.1 Specification 5.1.4.5
        /// Standard arc elements that use the defined arc role type MUST be identified with a usedOn element
        /// in the arcroleType element whenever they appear in standard extended links.  Note that custom arc 
        /// elements are not governed by this constraint, nor are standard arc elements that appear in custom 
        /// extended links.
        /// </remarks>
        private void ValidateUsedOnInArcRoleType()
        {
            Controller.Instance.RaiseModelChange(this, new ModelChangeEventArgs("Validating linkbases: UsedOnInArcRoleType"));

            foreach (LinkbaseDocument linkbase in dts.LinkbaseDocuments)
            {
                foreach (ExtendedLink extendedLink in linkbase.ExtendedLinks)
                {
                    foreach (Arc arc in extendedLink.Arcs)
                    {
                        foreach (ArcRoleType arcRoleType in dts.ArcRoleTypes)
                        {
                            if (IsStandardArc(arc.LocalName) && arc.ArcRole.AbsoluteUri.Equals(arcRoleType.ArcRoleUri.AbsoluteUri))
                            {
                                if (!arcRoleType.UsedOnReferences.Contains(XName.Get(arc.LocalName, arc.NamespaceUri)))
                                {
                                    log.Error("A new arcRole intended for used on a specific arc element, however it is used on a other arc(" + arc.LocalName + ").");
                                    dts.ValidatedLinkbases = false;
                                }
                            }
                        }
                    }
                }
            }
        }
        


        private void ValidateRoleRefAndArcRoleRef()
        {
            Controller.Instance.RaiseModelChange(this, new ModelChangeEventArgs("Validating linkbases: RoleRefAndArcRoleRef"));

            //RoleRef
            foreach (RoleRef roleRef in dts.RoleRefs)
            {
                if (roleRef.ParentDocument is LinkbaseDocument)
                {
                    string roleURI = roleRef.roleUri;

                    foreach (RoleRef roleRefToCompare in dts.RoleRefs)
                    {
                        if (roleRef != roleRefToCompare)
                        {
                            if (roleRef.ParentDocument == roleRefToCompare.ParentDocument && roleRefToCompare.roleUri.Equals(roleURI))
                            {
                                log.Error("Linkbase: " + roleRef.ParentDocument.FileName + " contains two role references to the same URI: " + roleURI);
                                dts.ValidatedLinkbases = false;
                            }
                        }
                    }
                }
            }

            //ArcRoleRef
            foreach (ArcRoleRef arcRoleRef in dts.ArcRoleRefs)
            {
                if (arcRoleRef.parentDocument is LinkbaseDocument)
                {
                    string arcRoleURI = arcRoleRef.arcroleURI;

                    foreach (ArcRoleRef arcRoleRefToCompare in dts.ArcRoleRefs)
                    {
                        if (arcRoleRef != arcRoleRefToCompare)
                        {
                            if (arcRoleRef.parentDocument == arcRoleRefToCompare.parentDocument && arcRoleRefToCompare.arcroleURI.Equals(arcRoleURI))
                            {
                                log.Error("Linkbase: " + arcRoleRef.parentDocument.FileName + " contains two arcRole references to the same URI: " + arcRoleURI);
                                dts.ValidatedLinkbases = false;
                            }
                        }
                    }
                }
            }

            // Arc role in arcroleRef element which is not declared
            foreach (ArcRoleRef arcRoleRef in dts.ArcRoleRefs)
            { 
                foreach (ArcRoleType arcRoleType in dts.ArcRoleTypes)
                {
                    if (arcRoleRef.arcroleURI.Equals(arcRoleType.ArcRoleUri.AbsoluteUri))
                    {
                        return;
                    }
                }

                log.Error("Linkbase: " + arcRoleRef.parentDocument.FileName + ". An arc element uses an arc role in arcroleRef element which is not declared. ArcRole: " + arcRoleRef.arcroleURI);
                dts.ValidatedLinkbases = false;
            }


        }


        private void ValidateArcRoleInArcs()
        {
            Controller.Instance.RaiseModelChange(this, new ModelChangeEventArgs("Validating linkbases: ArcRoleInArcs"));

            // Arc role in arc element which is not referenced in arcroleref.
            foreach (LinkbaseDocument linkbase in dts.LinkbaseDocuments)
            {
                foreach (ExtendedLink extendedLink in linkbase.ExtendedLinks)
                {
                    foreach (Arc arc in extendedLink.Arcs)
                    {
                        if (!IsDefaultArcRole(arc.ArcRole.AbsoluteUri))
                        {
                            foreach (ArcRoleRef arcRoleRef in dts.ArcRoleRefs)
                            {
                                if (linkbase.ThisXDocument == arcRoleRef.parentDocument.ThisXDocument && arcRoleRef.arcroleURI.Equals(arc.ArcRole.AbsoluteUri))
                                {
                                    return;
                                }
                            }

                            log.Error("Validation error: An arcrole in arc element which is not referenced in arcroleref. Linkbase: " + linkbase.FileName + " ArcRole: " + arc.ArcRole);
                            dts.ValidatedLinkbases = false;
                        }
                        else
                        {
                            
                        }
                    }
                }
            }
        }


        private bool IsDefaultArcRole(string arcRole)
        {
            string arcRolesDefault =
                                    "http://www.xbrl.org/2003/arcrole/essence-alias" +
                                    "http://www.xbrl.org/2003/arcrole/general-special" +
                                    "http://www.xbrl.org/2003/arcrole/similar-tuples" +
                                    "http://www.xbrl.org/2003/arcrole/requires-element" +
                                    "http://www.xbrl.org/2003/arcrole/parent-child" +
                                    "http://www.xbrl.org/2003/arcrole/child-parent" +
                                    "http://www.xbrl.org/2003/arcrole/summation-item" +
                                    "http://www.xbrl.org/2003/arcrole/concept-label" +
                                    "http://www.xbrl.org/2003/arcrole/concept-reference";

            if (arcRolesDefault.Contains(arcRole))
            {
                return true;
            }

            return false;
        }


        private bool IsCustomArcRole(string arcRole)
        {           
            foreach (ArcRoleType arcRoleType in dts.ArcRoleTypes)
            {
                if (arcRole.Equals(arcRoleType.ArcRoleUri.AbsoluteUri))
                {
                    return true;
                }
            }

            return false;
        }


        private bool IsStandardArc(string name)
        {
            string arcsDefault = "presentationArc" +
                                "calculationArc" +
                                "definitionArc" +
                                "labelArc" +
                                "referenceArc";

            if (arcsDefault.Contains(name))
            {
                return true;
            }

            return false;
        }


        private bool IsStandardExtendedLink(string name)
        {
            string linksDefault = "presentationLink" +
                                 "calculationLink" +
                                 "definitionLink" +
                                 "labelLink" +
                                 "referenceLink";

            if (linksDefault.Contains(name))
            {
                return true;
            }

            return false;
        }


        //Balance @	 Balance @	CalulationArc
        //From item	 To item		Illegal values
        //=========  =========	==============
        //debit		 debit		Negative (&lt; 0)
        //debit		 credit		Positive (&gt; 0)
        //credit	 debit		Positive (&gt; 0)
        //credit	 credit		Negative (&lt; 0)

        private void ValidateBalanceInSummation()
        {
            foreach (NetworkGraph ng in dts.NetworkGraphs)
            {
                // A definition network
                if (ng.TupleNG.Item1.Equals("calculationLink"))
                {
                    Controller.Instance.RaiseModelChange(this, new ModelChangeEventArgs("Validating BalanceInSummation. Network:" + ng.TupleNG.Item2));

                    foreach (Arc arc in ng.NetworkRelationships)
                    {
                        Concept from = LocateConcept(arc.FromLocator);
                        Concept to = LocateConcept(arc.ToLocator);

                        if (from.BalanceType == Concept.EnumBalanceType.Credit && to.BalanceType == Concept.EnumBalanceType.Credit ||
                            from.BalanceType == Concept.EnumBalanceType.Debit && to.BalanceType == Concept.EnumBalanceType.Debit)
                        {
                            if (arc.Weight <= 0)
                            {
                                //log.Debug("Validation error on linkbase", new XbrlException("Detected violations of the constraints among the balance attribute and calculation summation item arcs, value of weight attribute should be positive"));
                                log.Error("Detected violations of the constraints among the balance attribute and calculation summation item arcs, value of weight attribute should be positive");
                                dts.ValidatedLinkbases = false;
                            }
                        }

                        if (from.BalanceType == Concept.EnumBalanceType.Credit && to.BalanceType == Concept.EnumBalanceType.Debit ||
                            from.BalanceType == Concept.EnumBalanceType.Debit && to.BalanceType == Concept.EnumBalanceType.Credit)
                        {
                            if (arc.Weight >= 0)
                            {
                                //log.Debug("Validation error on linkbase", new XbrlException("Detected violations of the constraints among the balance attribute and calculation summation item arcs, value of weight attribute should be negative"));
                                log.Error("Detected violations of the constraints among the balance attribute and calculation summation item arcs, value of weight attribute should be negative");
                                dts.ValidatedLinkbases = false;
                            }
                        }
                    }
                }

            }
        }


        private Concept LocateConcept(Locator elementLocator)
        {
            foreach (TaxonomySchema currentSchema in this.dts.TaxonomySchemas)
            {
                var foundElement = currentSchema.LocateConcept(elementLocator);

                if (foundElement != null)
                {
                    return foundElement;
                }
            }

            return null;
        }       
        

        private void ValidateCalculationLinkbase()
        {
            ValidateNumericType();

            ValidateArcRoleInCalculationLinkbase();
        }


        private void ValidateNumericType()
        {
            Controller.Instance.RaiseModelChange(this, new ModelChangeEventArgs("Validating calculation Linkbases: NumericType..."));

            foreach (NetworkGraph ng in dts.NetworkGraphs)
            {
                if (ng.TupleNG.Item1.Equals("calculationLink"))
                {
                    foreach (Arc currentCalculationArc in ng.NetworkRelationships)
                    {
                        Concept summationConcept = dts.GetConceptByID(HttpUtility.UrlDecode(currentCalculationArc.FromLocator.HrefResourceId));

                        if (!summationConcept.SchemaElement.ElementSchemaType.IsNumeric())
                        {
                            log.Error("Validation error: Summation side of arc not numeric. Summation: " + summationConcept.Name);
                            dts.ValidatedLinkbases = false;
                        }

                        Concept contributingConcept = dts.GetConceptByID(HttpUtility.UrlDecode(currentCalculationArc.ToLocator.HrefResourceId));

                        if (!contributingConcept.SchemaElement.ElementSchemaType.IsNumeric() &&
                            !IsCustomArcRole(currentCalculationArc.ArcRole.AbsoluteUri))
                        {
                            log.Error("Validation error: Item side of arc not numeric. Item: " + contributingConcept.Name);
                            dts.ValidatedLinkbases = false;
                        }
                    }
                }
            }
        }


        private void ValidateArcRoleInCalculationLinkbase()
        {
            Controller.Instance.RaiseModelChange(this, new ModelChangeEventArgs("Validating calculation Linkbases: ArcRoleInCalculationLinkbase..."));

            foreach (LinkbaseDocument linkbase in dts.LinkbaseDocuments)
            {
                foreach (ExtendedLink extendedLink in linkbase.ExtendedLinks)
                {
                    if (extendedLink.LocalName.Equals("calculationLink"))
                    {
                        foreach (Arc arc in extendedLink.Arcs)
                        {
                            if (!arc.ArcRole.AbsoluteUri.Equals("http://www.xbrl.org/2003/arcrole/summation-item") &&
                                !arc.ArcRole.AbsoluteUri.Equals("http://xbrl.org/int/dim/arcrole/aggregator-contributor") &&
                                !IsCustomArcRole(arc.ArcRole.AbsoluteUri))
                            {
                                log.Error("Validation error on linkbase: The calculation linkbase uses invalid arcrole. arc.ArcRole: " + arc.ArcRole);
                                dts.ValidatedLinkbases = false;
                            }
                        }
                    }
                }
            }
        }


        private void ValidateOverrideLabelAndReferenceLinkbases()
        {
            Controller.Instance.RaiseModelChange(this, new ModelChangeEventArgs("Validating linkbases: OverrideLabelAndReferenceLinkbases"));

            foreach (Tuple<string, string, string, string> tuple in dts.BaseSetsArcs.Keys)
            {
                if ((tuple.Item1.Equals("labelLink")) || (tuple.Item1.Equals("referenceLink")))
                {
                    HashSet<Arc> networkRelationship = dts.BaseSetsArcs[tuple];
                    List<Arc> tempNetworkRelationship = new List<Arc>(networkRelationship);

                    if (networkRelationship.Count > 5000)
                    {
                        return;
                    }

                    HashSet<Arc> arcsExcluded = new HashSet<Arc>();

                    //for (int i = 0; i < networkRelationship.Count; i++)
                    //{
                    //    ValidateArcRelationship(networkRelationship[i]);

                    //    for (int j = 0; j < networkRelationship.Count; j++)
                    //    {
                    //        if (i != j && networkRelationship[i].ExtendedLinkParent.Lang.Equals(networkRelationship[j].ExtendedLinkParent.Lang))
                    //        {
                    //            //isProhibited
                    //            if (networkRelationship[i].IsProhibitedByArc(networkRelationship[j]))
                    //            {
                    //                if (networkRelationship.Contains(networkRelationship[i]))
                    //                {
                    //                    log.Warn("Removing prohibited arc: " + networkRelationship[i].LocalName + "-" + networkRelationship[i].FromId + " to " + networkRelationship[i].ToId + "-" + networkRelationship[i].ArcRole);
                    //                    arcsExcluded.Add(networkRelationship[i]);
                    //                }

                    //                if (networkRelationship.Contains(networkRelationship[j]))
                    //                {
                    //                    log.Warn("Removing prohibiting arc: " + networkRelationship[j].LocalName + "-" + networkRelationship[j].FromId + " to " + networkRelationship[j].ToId + "-" + networkRelationship[j].ArcRole);
                    //                    arcsExcluded.Add(networkRelationship[j]);
                    //                }
                    //            }

                    //            //isOverridden
                    //            if (networkRelationship[i].IsOverriddenByArc(networkRelationship[j]))
                    //            {
                    //                if (networkRelationship.Contains(networkRelationship[i]))
                    //                {
                    //                    log.Warn("Removing overridden arc: " + networkRelationship[i].LocalName + "-" + networkRelationship[i].FromId + " to " + networkRelationship[i].ToId + "-" + networkRelationship[i].ArcRole);
                    //                    arcsExcluded.Add(networkRelationship[i]);
                    //                }
                    //            }
                    //        }
                    //    }
                    //}

                    //dts.BaseSetsArcs[tuple].RemoveAll(arc => arcsExcluded.Contains(arc));

                    foreach (Arc arc in tempNetworkRelationship)
                    {
                        foreach (Arc compareArc in tempNetworkRelationship)
                        {
                            if (arc != compareArc && arc.ExtendedLinkParent.Lang.Equals(compareArc.ExtendedLinkParent.Lang))
                            {
                                ValidateArcRelationship(arc);

                                //isProhibited
                                if (arc.IsProhibitedByArc(compareArc))
                                {
                                    if (networkRelationship.Contains(arc))
                                    {
                                        log.Debug("Removing prohibited arc: " + arc.LocalName + "-" + arc.FromId + " to " + arc.ToId + "-" + arc.ArcRole);
                                        dts.BaseSetsArcs[tuple].Remove(arc);
                                    }
                                    if (networkRelationship.Contains(arc))
                                    {
                                        log.Debug("Removing prohibiting arc: " + compareArc.LocalName + "-" + compareArc.FromId + " to " + compareArc.ToId + "-" + compareArc.ArcRole);
                                        dts.BaseSetsArcs[tuple].Remove(compareArc);
                                    }
                                }
                                //isOverridden
                                if (arc.IsOverriddenByArc(compareArc))
                                {
                                    if (networkRelationship.Contains(arc))
                                    {
                                        log.Debug("Removing overridden arc: " + arc.LocalName + "-" + arc.FromId + " to " + arc.ToId + "-" + arc.ArcRole);
                                        dts.BaseSetsArcs[tuple].Remove(arc);
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }


        /// <remarks>
        /// XBRL 2.1 Specification 5.2.2.3 
        /// The label elements that participate in a relationship described by a labelArc element MUST be [XLINK] local resources
        /// except when the use attribute on the labelArc is “prohibited”, in which case the label elements MAY be [XLINK] local
        /// resources and/or [XLINK] remote resources.
        /// </remarks>
        private void ValidateArcRelationship(Arc arc)
        {
            if (!arc.UseAttribute.Equals("prohibited") && arc.ToLocator != null)
            {
                log.Error("Validation error: The label elements that participate in a relationship described by a labelArc element MUST be local resources.");
                dts.ValidatedLinkbases = false;
            }

            if (arc.UseAttribute.Equals("prohibited") && (arc.ToLocator != null && dts.GetConceptByID(arc.ToLocator.HrefResourceId) != null))
            {
                log.Error("Validation error: When the use attribute on the labelArc is “prohibited”, in which case the label elements MAY be local resources and/or remote resources, not an item in a schema.");
                dts.ValidatedLinkbases = false;
            }
        }


        private void ValidateArcsDuplication()
        {
            Controller.Instance.RaiseModelChange(this, new ModelChangeEventArgs("Validating linkbases: ArcsDuplication"));

            foreach (LinkbaseDocument linkbase in dts.LinkbaseDocuments)
            {
                foreach (ExtendedLink extendedLink in linkbase.ExtendedLinks)
                {
                    for (int i = 0; i < 1; i++)
                    {
                        for (int j = 0; j < extendedLink.Arcs.Count; j++)
                        {
                            if (i != j)
                            {
                                if (extendedLink.Arcs[i].FromId.Equals(extendedLink.Arcs[j].FromId) &&
                                    extendedLink.Arcs[i].ToId.Equals(extendedLink.Arcs[j].ToId))
                                {
                                    log.Error("Validation error: Arc-type elements connecting the same 'from' and 'to' labels MUST appear in same extended-type link element.");
                                    dts.ValidatedLinkbases = false;
                                }
                            }
                        }
                    }
                }

            }
        }


        // 5.2.4.2.1   
        // The xlink:role attribute value on the label extended link containing the preferred label and the xlink:role 
        // attribute value on the presentation extended link containing the presentationArc element do not have to be equal.

        private void ValidatePreferredLabelAttribute()
        {
            foreach (LinkbaseDocument linkbase in dts.LinkbaseDocuments)
            {
                foreach (ExtendedLink extendedLink in linkbase.ExtendedLinks)
                {
                    if (extendedLink.LocalName.Equals("presentationLink"))
                    {
                        Controller.Instance.RaiseModelChange(this, new ModelChangeEventArgs("Validating PreferredLabelAttribute. linkbase:" + linkbase.FileName));

                        foreach (Arc arc in extendedLink.Arcs)
                        {
                            if (arc.ArcRole.AbsoluteUri.Equals("http://www.xbrl.org/2003/arcrole/parent-child"))
                            {
                                IEnumerable<XAttribute> thisArcAttributes = arc.ArcElement.Attributes().Where(a => a.Name.LocalName.Equals("preferredLabel"));

                                if (thisArcAttributes.Count() > 0)
                                {
                                    XAttribute attr = thisArcAttributes.FirstOrDefault();
                                    string preferredLabel = attr.Value;

                                    Concept childConcept = dts.GetConceptByID(arc.ToLocator.HrefResourceId);
                                    List<Label> labels = GetLabelResources(childConcept);

                                    foreach (Label label in labels)
                                    {
                                        if (label.Role.Equals(preferredLabel))
                                        {
                                            goto nextArc;
                                        }
                                    }

                                    log.Error("Linkbase:" + linkbase.FileName + ". The value of the preferredLabel attribute (" + preferredLabel + ") MUST be equal to an xlink:role attribute value on a label resource that is the target of a concept-label arc from the presentationArc element’s child concept(" +arc.ToId + ")");
                                    dts.ValidatedLinkbases = false;
                                }
                            }

                        nextArc: ;
                        }
                    }
                }
            }
        }


        private void ValidateArcsInDefinitionArcRoles()
        {
            Controller.Instance.RaiseModelChange(this, new ModelChangeEventArgs("Validating linkbases: ArcsInDefinitionArcRoles"));

            foreach (LinkbaseDocument currentLinkbaseDocument in dts.LinkbaseDocuments)
            {
                if (currentLinkbaseDocument.RoleLinkbaseRef.Equals("http://www.xbrl.org/2003/role/definitionLinkbaseRef"))
                {
                    foreach (ExtendedLink currentDefinitionLink in currentLinkbaseDocument.ExtendedLinks)
                    {
                        foreach (Arc CurrentDefinitionArc in currentDefinitionLink.Arcs)
                        {
                            switch (CurrentDefinitionArc.ArcRole.AbsoluteUri)
                            {
                                case Constants.DEFINITIONEssenceAliasArcrole:
                                    ValidateEssenceAliasedFacts(CurrentDefinitionArc);
                                    break;
                                case Constants.DEFINITIONRequiresElementArcrole:
                                    //ValidateRequiresElementFacts(CurrentDefinitionArc);
                                    break;
                                default:
                                    break;
                            }
                        }
                    }
                }
            }
        }


        // Both the alias concept and essence concept of an arc MUST have the same item type and the same value 
        // for the periodType attribute. Also, if the balance attribute is present on both the alias concept and
        // essence concept of an arc, it MUST have the same value for both concepts.
        // There is no similar requirement if the balance attribute is absent from either or both of the concepts.

        private void ValidateEssenceAliasedFacts(Arc essenceAliasDefinitionArc)
        {
            Concept fromConcept = dts.GetConceptByID(essenceAliasDefinitionArc.FromLocator.HrefResourceId);

            Concept toConcept = dts.GetConceptByID(essenceAliasDefinitionArc.ToLocator.HrefResourceId);

            if (!fromConcept.PeriodTypeString.Equals(toConcept.PeriodTypeString) ||
                !fromConcept.TypeName.Equals(toConcept.TypeName) ||
                 (!string.IsNullOrEmpty(fromConcept.BalanceString) && !string.IsNullOrEmpty(toConcept.BalanceString) && 
                  !fromConcept.BalanceString.Equals(toConcept.BalanceString)))
            {
                string linkbase = essenceAliasDefinitionArc.ExtendedLinkParent.ParentDocument.FileName;
                log.Error(linkbase + ": The alias concept(" + fromConcept.Name + ") and essence concept(" + toConcept.Name + ") have different values for the periodType and balance attributes.");
                dts.ValidatedLinkbases = false;
            }
        }


        /// <remarks>
        /// XBRL 2.1 Specification 4.3.4 
        /// The LinkbaseRef XLink role attribute value must	match the linkbase found at its target. 
        /// </remarks>
        private void ValidateRoleLinkbaseRef()
        {
            Controller.Instance.RaiseModelChange(this, new ModelChangeEventArgs("Validating linkbases: RoleLinkbaseRef"));

            foreach (LinkbaseDocument linkbase in dts.LinkbaseDocuments)
            {
                switch (linkbase.RoleLinkbaseRef)
                {
                    case "http://www.xbrl.org/2003/role/calculationLinkbaseRef":
                        VerifyRoleLinkbaseRef(linkbase, "calculationLink");
                        break;
                    case "http://www.xbrl.org/2003/role/definitionLinkbaseRef":
                        VerifyRoleLinkbaseRef(linkbase, "definitionLink");
                        break;
                    case "http://www.xbrl.org/2003/role/labelLinkbaseRef":
                        VerifyRoleLinkbaseRef(linkbase, "labelLink");
                        break;
                    case "http://www.xbrl.org/2003/role/presentationLinkbaseRef":
                        VerifyRoleLinkbaseRef(linkbase, "presentationLink");
                        break;
                    case "http://www.xbrl.org/2003/role/referenceLinkbaseRef":
                        VerifyRoleLinkbaseRef(linkbase, "referenceLink");
                        break;
                    default:
                        break;
                }
            }
        }


        private void VerifyRoleLinkbaseRef(LinkbaseDocument linkbase, string elementLinkName)
        {
            foreach (ExtendedLink extendedLink in linkbase.ExtendedLinks)
            {
                if (!extendedLink.LocalName.Equals(elementLinkName))
                {
                    log.Error("Validation error: The LinkbaseRef XLink role attribute value:" + linkbase.RoleLinkbaseRef + " must match the linkbase found at its target:" + elementLinkName);
                    dts.ValidatedLinkbases = false;
                    break;
                }
            }
        }


        /// <remarks>
        /// XBRL 2.1 Specification 3.5.3.7.2 
        /// A locator MUST have an xlink:href attribute. 
        /// The URI MUST point to an XML document or to one or more XML fragments within an XML document. 
        /// </remarks>
        private void ValidateHrefResolution()
        {
            foreach (LinkbaseDocument linkbase in dts.LinkbaseDocuments)
            {
                if (!linkbase.RoleLinkbaseRef.Equals("http://www.xbrl.org/2003/role/labelLinkbaseRef") &&
                    !linkbase.RoleLinkbaseRef.Equals("http://www.xbrl.org/2003/role/referenceLinkbaseRef"))
                {
                    Controller.Instance.RaiseModelChange(this, new ModelChangeEventArgs("Validating HrefResolution in the linkbase:" + linkbase.FileName));

                    foreach (ExtendedLink extendedLink in linkbase.ExtendedLinks)
                    {
                        foreach (Locator locator in extendedLink.Locators)
                        {
                            if (!string.IsNullOrEmpty(locator.Href))
                            {
                                string id = CompileXPointer(locator.HrefResourceId, locator.HrefDocumentUri);

                                if (!id.Equals(string.Empty) && locator.HrefDocumentUri.EndsWith(".xsd"))
                                {
                                    Concept concept = dts.GetConceptByID(id);
                                    RoleType roleType = dts.GetRoleType(id);

                                    if (concept == null && roleType == null)
                                    {
                                        log.Error(linkbase.FileName + ": A URI reference appearing in an href attribute(" + locator.Href + ") must resolve to an XML Schema element");
                                        dts.ValidatedLinkbases = false;
                                    }
                                }
                            }
                            else
                            {
                                if (IsStandardExtendedLink(extendedLink.LocalName))
                                {
                                    log.Error("Validation error: An empty href poses a problem. locator: " + locator.Label);
                                    dts.ValidatedLinkbases = false;
                                }
                            }
                        }

                        bool isFound = false;

                        foreach (Arc arc in extendedLink.Arcs)
                        {
                            foreach (Resource resource in extendedLink.Resources)
                            {
                                if (arc.ToId.Equals(resource.Label))
                                {
                                    isFound = true;
                                }
                            }

                            foreach (Locator locator in extendedLink.Locators)
                            {
                                if (arc.ToId.Equals(locator.Label))
                                {
                                    isFound = true;
                                }
                            }

                            if (!isFound)
                            {
                                log.Error("Validation error: A URI reference appearing in an href attribute must resolve to an xlink resource-type element");
                                dts.ValidatedLinkbases = false;
                            }
                        }
                    }
                }
            }
        }


        /// <remarks>
        /// XBRL 2.1 Specification 3.5.4 
        /// The only scheme allowed for scheme-based pointers in XBRL links is the element scheme [ELEMENT].
        /// </remarks>
        private string CompileXPointer(string xpointerExpression, string schema)
        {
            if (string.IsNullOrEmpty(xpointerExpression))
            {
                return "";
            }

            string expression = xpointerExpression.Replace("element(", "").Replace(")", "");
            // /1/3
            if (expression.IndexOf('/') >= 0)
            {
                int positionElement = Int32.Parse(expression.Substring(expression.LastIndexOf('/') + 1));

                foreach (TaxonomySchema currentSchema in this.dts.TaxonomySchemas)
                {
                    if (currentSchema.FileName == schema)
                    {
                        XElement element = currentSchema.ThisXDocument.Root.Elements().ElementAt(positionElement - 1);

                        if (element.Name.LocalName.Equals("element"))
                        {
                            expression = element.Attribute("id").Value;
                        }
                        else
                        {
                            log.Error("Validation error: An element scheme in an X pointer in a label linkbase locator that does not resolve to a concept.");
                            dts.ValidatedLinkbases = false;
                            return "";
                        }
                    }
                }

                return expression;
            }
            else
            {
                return expression;
            }
        }


        private List<Label> GetLabelResources(Concept childConcept)
        {
            List<Label> labels = new List<Label>();

            foreach (LinkbaseDocument linkbase in dts.LinkbaseDocuments)
            {
                if (linkbase.RoleLinkbaseRef.Equals("http://www.xbrl.org/2003/role/labelLinkbaseRef"))
                {
                    foreach (ExtendedLink extendedLink in linkbase.ExtendedLinks)
                    {
                        foreach (Locator locator in extendedLink.Locators)
                        {
                            if (locator.HrefResourceId.Equals(childConcept.Id))
                            {
                                foreach (Arc arc in extendedLink.Arcs)
                                {
                                    if (arc.FromId.Equals(locator.Label))
                                    {
                                        labels.AddRange(GetLabels(arc.ToId, extendedLink));
                                    }
                                }
                            }
                        }
                    }
                }
            }

            return labels;
        }


        private List<Label> GetLabels(string toId, ExtendedLink extendedLink)
        {
            List<Label> labels =new List<Label>();

            foreach (Resource resource in extendedLink.Resources)
            {
                if (resource.Label.Equals(toId))
                {
                    labels.Add(resource as Label);
                }
            }

            return labels;
        }

    }
}
